REM synthetic spectrum analyser
REM Paul Cuthbertson 1993
REM filename spec_an2.bas

REM constants
REM number of samples
LENGTH = 128
REM number of samples less one (saves calculation)
NONE = LENGTH - 1
REM the depth of decomposition of the transform
REM (log to the base 2 of the sample length)
DEPTH = CINT(LOG(LENGTH) / LOG(2))
REM twice PI
TWOPI = 6.283185

REM control constants
REM horizontal pixels per displayed point
HRES = 1280 / LENGTH
REM useful vertical pixels
VRES = 440
REM vga screen value
SCREENVAL = 12
REM 1 for logarithmic vertical scale, 0 for linear
LOGC = 0
REM 1 for power display, 0 for amplitude
POWER = 0
REM 1 for autoscaling, 0 for clipping
ASCALE = 1

REM addresses
REM base address
B = 3 * 256 + 14 * 16
REM counter channels zero, one and two
COUNTER0 = B
COUNTER1 = B + 1
COUNTER2 = B + 2
REM counter control register
CONREG = B + 3
REM analogue i/o ports
PORT1 = B + 4
PORT2 = B + 5
REM software strobe
STROBE = B + 6
REM status register
STATUS = B + 7

REM set up
SCREEN SCREENVAL
LOCATE 1, 1
PRINT "Initialising..."

REM arrays
REM sample array doubles as working array
DIM SAMPLES(LENGTH)
REM trigonometric lookup (saves calculation)
DIM COSINES(LENGTH)
DIM SINES(LENGTH)
REM working arrays for fft
DIM IMAGINARY(LENGTH)
DIM REAL(LENGTH)

REM fill trigonometric arrays
FOR N = 0 TO NONE
  COSINES(N) = COS(TWOPI * N / LENGTH)
  SINES(N) = SIN(TWOPI * N / LENGTH)
  NEXT

REM strobe gen. mode 2, binary, double access, counter 0
OUT CONREG, 3 * 16 + 4
REM load counter zero with count
OUT COUNTER0, 4
OUT COUNTER0, 2

REM start of main program loop
MajorLoop:

REM square wave gen. mode 3, binary, double access, counter 2
REM might as well leave this in if not using counter #2 for anything else
OUT CONREG, 11 * 16 + 6
REM load counter two with count
OUT COUNTER2, 124 + RND(1) * 16
OUT COUNTER2, 0

REM sampling
REM throw away first sample
S = INP(PORT1)
FOR X = 1 TO LENGTH
REM wait for ADC #1 interrupt
  WHILE (INP(STATUS) AND 1) = 1
  WEND
REM grab sample
  SAMPLES(X) = INP(PORT1)
REM don't waste any time cleaning up etc., grab next
  NEXT
REM clean samples
FOR X = 1 TO LENGTH
  IF SAMPLES(X) > 127 THEN SAMPLES(X) = SAMPLES(X) - 256
  NEXT

REM set average
AVERAGE = 0

REM clear imaginary array
FOR N = 1 TO LENGTH
  IMAGINARY(N) = 0
REM update average
  AVERAGE = AVERAGE + SAMPLES(N) / LENGTH
  NEXT

REM fill real array
FOR N = 1 TO LENGTH
REM apply raised cosine bell (Hanning) window and subtract average
  REAL(N) = SAMPLES(N) * (1 - COSINES(N)) / 2 - AVERAGE
  NEXT

REM calculation
REM initial step for index into trigonometric arrays is 1
TRIGINDEXSTEP = 1
REM initial distance between sections is whole sample length
JUMPSIZE = LENGTH
REM initial section length is half sample size
SPAN = LENGTH / 2
REM for each successive decomposition
FOR DEPTHCOUNT = 1 TO DEPTH
REM reset trig. table index  
  TRIGINDEX = 0
REM for every starting point in the section
  FOR START = 1 TO SPAN
REM look up the trig. tables  
    cosine = COSINES(TRIGINDEX)
    SINE = SINES(TRIGINDEX)
REM update the trig. table index
    TRIGINDEX = TRIGINDEX + TRIGINDEXSTEP
REM for all indices jumsize apart, starting at start
    FOR INDEX1 = START TO LENGTH STEP JUMPSIZE
REM second index is section length apart from first index
      INDEX2 = INDEX1 + SPAN
REM effectively we are scanning the working arrays again and again
REM using smaller step sizes and smaller lengths each time.
REM to use an example with 8 data points (samples) and a depth of 3
REM the first time around we scan the first half of the array using index #1
REM and combine it with the corresponding upper half using index #2:
REM 1&5, 2&6, 3&7, 4&8
REM For the next scan we scan in quarters rather than halves:
REM 1&3, 5&7, 2&4, 6&8
REM for the very last scan this boils down to taking adjacent pairs of entries:
REM 1&2, 3&4, 5&6, 7&8
REM print the values of INDEX1 and INDEX2 to see this happening
REM calculate the differences between the two entries in each working array
      REALDIFF = REAL(INDEX1) - REAL(INDEX2)
      IMAGINARYDIFF = IMAGINARY(INDEX1) - IMAGINARY(INDEX2)
REM sum the first and second entries into the first
      REAL(INDEX1) = REAL(INDEX1) + REAL(INDEX2)
      IMAGINARY(INDEX1) = IMAGINARY(INDEX1) + IMAGINARY(INDEX2)
REM calculate new entries for the second index
      REAL(INDEX2) = cosine * REALDIFF + SINE * IMAGINARYDIFF
      IMAGINARY(INDEX2) = cosine * IMAGINARYDIFF - SINE * REALDIFF
REM next index
      NEXT
REM next starting point
    NEXT
REM make trig. table index step double for next time
  TRIGINDEXSTEP = TRIGINDEXSTEP * 2
REM smaller section length and distance between sections at the next level
REM (section length is always half that of distance)
  SPAN = SPAN / 2
  JUMPSIZE = JUMPSIZE / 2
REM next level
  NEXT

REM unscramble mirror image
REM initial value of the bit reversed counter is 1
INDEX = 1
REM for all entries into the working array apart from the last
FOR COUNT = 1 TO NONE
REM if count >= index then do not swap
REM (otherwise we'd be swapping and swapping back again)
  IF COUNT >= INDEX THEN GOTO BitReversedCount

REM swapping, it's obvious how this works
  SWAPREAL = REAL(INDEX)
  SWAPIMAGINARY = IMAGINARY(INDEX)
  REAL(INDEX) = REAL(COUNT)
  IMAGINARY(INDEX) = IMAGINARY(COUNT)
  REAL(COUNT) = SWAPREAL
  IMAGINARY(COUNT) = SWAPIMAGINARY

BitReversedCount:
REM the net result of this section is to start at the most
REM significant end of the counter, resetting bits that are set.
REM the first reset (0) bit encountered is set and the loop ends.
REM set the most significant bit of the number in a shift register
S = LENGTH / 2
REM while a bit of the reversed counter is set
WHILE S < INDEX
REM reset it
INDEX = INDEX - S
REM shift the test bit right one place
S = S / 2
REM and test again
WEND
REM add the first unset bit into the reversed counter
INDEX = INDEX + S
REM next entry
NEXT
REM end of sorting

REM power and amplitude calculations
REM set max. to a very tiny number and min. to a large number
MAXIMUM = 9.999999E-39
MINIMUM = 1E+38
REM only the first half of the working entries is useful, the other
REM half is just a mirror image
REM re-use the first half of the samples array for output results
FOR N = 1 TO LENGTH / 2
REM power calculation
  SAMPLES(N) = IMAGINARY(N) ^ 2 + REAL(N) ^ 2
REM if an amplitude is needed, take square root
  IF POWER = 0 THEN SAMPLES(N) = SQR(SAMPLES(N))
REM if linear vertical scale, do not take the log
  IF LOGC = 0 THEN GOTO BypassLog
REM if entry is zero, avoid errors by making very small
  IF SAMPLES(N) = 0 THEN SAMPLES(N) = 9.999999E-39
REM take the log
  SAMPLES(N) = LOG(SAMPLES(N))
BypassLog:
  NEXT

REM auto scaling
REM for all the useful entries
FOR N = 1 TO LENGTH / 2
REM update minimum where entry was less than previous
  IF SAMPLES(N) < MINIMUM THEN MINIMUM = SAMPLES(N)
REM update maximum where entry was > previous
  IF SAMPLES(N) > MAXIMUM THEN MAXIMUM = SAMPLES(N)
REM if not auto scaling, clip off the entry at the height of the screen
  IF ASCALE = 0 AND SAMPLES(N) > VRES THEN SAMPLES(N) = VRES
REM next entry
  NEXT
REM rangeis the difference between max. and min.
RANGE = MAXIMUM - MINIMUM
REM if not auto scaling however the range is the height of the screen
IF ASCALE = 0 THEN RANGE = VRES
REM the minimum used in calculations is the found minimum...
MIN = MINIMUM
REM ...unless auto scale is off when it becomes zero
IF ASCALE = 0 THEN MIN = 0

REM output
CLS
REM calculate first y co-ordinate
FROMY = VRES * (1 - (SAMPLES(1) - MIN) / RANGE)
REM for all the rest of the useful entries
FOR N = 2 TO LENGTH / 2
REM calculate the next y co-ordinate
  TOY = VRES * (1 - (SAMPLES(N) - MIN) / RANGE)
REM draw a line between the two points
  LINE ((N - 1) * HRES, FROMY)-(N * HRES, TOY)
REM make the old y co-ordinate the most recent one
  FROMY = TOY
REM next entry
  NEXT

REM again
GOTO MajorLoop

